#include "PerlinNoise.h"
#include "../stdafx.h"
#include <stdio.h>
#include "../math/Math.h"
#include <stdlib.h>
#include <time.h>

PerlinNoise::PerlinNoise() : 
				 gradient1d(0),
				 gradient2d(0),
				 gradient3d(0),
				 permutation(0),
				 rankCount(0),
				 frequency(0),
				 
				 amplitude(0),
				 persistence(0)
{
	InitPerlinNoise();
}

PerlinNoise::~PerlinNoise()
{
	CleanupPerlinNoise();
}

int PerlinNoise::GetRankCount() {
	return rankCount;
}

void PerlinNoise::InitPerlinNoise() {
	/* Cleanup */
	ReleaseNewArray(gradient1d);
	ReleaseNewArray(gradient2d);
	ReleaseNewArray(gradient3d);
	ReleaseNewArray(permutation);
	ReleaseNewArray(frequency);
	ReleaseNewArray(persistence);
	ReleaseNewArray(amplitude);
	
	/* set gradientSize */
	rankCount = 7;
	gradientSize = Math::Power(2, rankCount)+1;	/* gradient from 0 - 2^rankCount as frequency goes from 2^0 to 2^rankCount
		
	/* allocate */	
	gradient1d = new Gradient1d[gradientSize];
	gradient2d = new Gradient2d[gradientSize];
	gradient3d = new Gradient3d[gradientSize];
	permutation = new int[gradientSize];
	frequency = new int[rankCount];
	
	/* Generate gradient */
	srand(time(NULL));
	for (int i = 0; i < gradientSize; i++) {
		gradient1d[i].x = (2 * (float)rand() / RAND_MAX) - 1.0f;
		
		gradient2d[i].x = (2 * (float)rand() / RAND_MAX) - 1.0f; //from -1 to 1
		gradient2d[i].y = (2 * (float)rand() / RAND_MAX) - 1.0f;
		
		gradient3d[i].x = (2 * (float)rand() / RAND_MAX) - 1.0f;
		gradient3d[i].z = (2 * (float)rand() / RAND_MAX) - 1.0f;
		gradient3d[i].t = (2 * (float)rand() / RAND_MAX) - 1.0f;
	}
	
	/* Generate permutation */
	for (int i = 0; i < gradientSize; i++)
		permutation[i] = i;
	
	int j, tmp;
	for (int i = 0; i < gradientSize; i++) {
		j = rand() % gradientSize;
		
		//swap
		tmp = permutation[i];
		permutation[i] = permutation[j];
		permutation[j] = tmp;
	}		
	
	/* number of frequency for each rank */
	for (int i = 0; i < rankCount; i++) {
		frequency[i] = Math::Power(2, i); //node0 = rank1 = 2 frequency
	}
	
	/* persistence */
	persistence = new float[rankCount];
	for (int i = 0; i < rankCount; i++) {
		//persistence[i] = (1.0f / (i+1));
		persistence[i] = 0.5f;
	}
	/* amplitude */
	amplitude = new float[rankCount];
	for (int i = 0; i < rankCount; i++) {
		amplitude[i] = Math::Power(persistence[i], i);
		//amplitude[i] = 1.0f / Math::Power(2, i);
	}
}

void PerlinNoise::CleanupPerlinNoise() {	
	ReleaseNewArray(gradient1d);
	ReleaseNewArray(gradient2d);
	ReleaseNewArray(gradient3d);
	ReleaseNewArray(permutation);
	ReleaseNewArray(frequency);
	ReleaseNewArray(persistence);
	ReleaseNewArray(amplitude);
}

/* 1D */
Gradient1d PerlinNoise::Gradient(int x, int frequency) {
	int xx = x % frequency;
	int i = permutation[xx] % frequency;
	return gradient1d[i];
}

int PerlinNoise::PerlinNoise1d(int x) {
	return 0;
}

float PerlinNoise::PerlinNoise1d(float xp, int frequency/*float x, int frequency*/) {
	//float xp = x * (frequency - 1);
	int x0 = (int)xp;
	int x1 = x0 + 1;
	
	Gradient1d grad0 = Gradient(x0, frequency);
	Gradient1d grad1 = Gradient(x1, frequency);
	
	float dx0 = xp - x0;
	float dx1 = xp - x1;
	
	float f0 = grad0.x*dx0;
	float f1 = grad1.x*dx1;
	
	float coefX = 3*dx0*dx0 - 2*dx0*dx0*dx0;
	float p = f0 + coefX*(f1 - f0);
	return p;
}

/* 2D Perlin noise ========================= */
Gradient2d PerlinNoise::Gradient(int x, int z, int frequency) {
	int xx = x % frequency;
	int zz = z % frequency;
	int i = permutation[(xx + permutation[zz]) % frequency] % frequency;
	return gradient2d[i];
}

int PerlinNoise::PerlinNoise2d(int x, int z, int width, int height) {
	float total = 0;
	for (int i = 0; i < rankCount; i++) {		
		total += PerlinNoise2d((float)x / (width-1) * frequency[i], (float)z / (height-1) * frequency[i], frequency[i]) /* amplitude */ * amplitude[i];
	}
	
	return (int)((total + 1.0f) * 255 / 2.0f);
}


/* x, z in [0, 1] */
float PerlinNoise::PerlinNoise2d(float xp, float zp, int frequency) {	
	//float xp = x * (frequency - 1);
	//float zp = z * (frequency - 1);
	
	/* 4 surrounding points */
	int x0 = (int)xp;
	int z0 = (int)zp;
	int x1 = x0 + 1;
	int z1 = z0 + 1;	
	
	Gradient2d grad00 = Gradient(x0, z0, frequency);
	Gradient2d grad10 = Gradient(x1, z0, frequency);
	Gradient2d grad01 = Gradient(x0, z1, frequency);
	Gradient2d grad11 = Gradient(x1, z1, frequency);
	
	float f00 = grad00.x * (xp - x0) + grad00.y * (zp - z0); 
	float f10 = grad10.x * (xp - x1) + grad10.y * (zp - z0);
	float f01 = grad01.x * (xp - x0) + grad01.y * (zp - z1);
	float f11 = grad11.x * (xp - x1) + grad11.y * (zp - z1);
	
	float dx0 = xp - x0;
	float dz0 = zp - z0;
	
	/* Bi-cubic interpolation */
	float coefX = 3*dx0*dx0 - 2*dx0*dx0*dx0;
	float coefZ = 3*dz0*dz0 - 2*dz0*dz0*dz0;
	float p1 = f00 + coefX*(f10 - f00);
	float p2 = f01 + coefX*(f11 - f01);
	//float p1 = f00 + coefX*(f01 - f00);
	//float p2 = f10 + coefX*(f11 - f10);
	float p3 = p1 + coefZ*(p2 - p1);
	return p3;
}

/* 3D Perlin Noise ============================== */
Gradient3d PerlinNoise::Gradient(int x, int z, int t, int frequency) {
	/* wrap around: interpolate between 255 and 0 for tileable */
	int xx = x % frequency;
	int zz = z % frequency;
	int tt = t % frequency;
	
	int i = permutation[(xx + permutation[(zz + permutation[tt]) % frequency]) % frequency] % frequency;
	return gradient3d[i];
}

/* x, z, t in [0, 1] */
float PerlinNoise::PerlinNoise3d(float xp, float zp, float tp, int frequency) {
	//float xp = x * (frequency - 1);
	//float zp = z * (frequency - 1);
	//float tp = t * (frequency - 1);
	
	/* 8 surrounding points */ 
	int x0 = (int)xp;
	int z0 = (int)zp;
	int t0 = (int)tp;
	int x1 = (x0 + 1); /* (!) can be overflow but wrapped around in Gradient() function */
	int z1 = (z0 + 1);
	int t1 = (t0 + 1);	
	
	/* Get gradient vectors */
	Gradient3d grad000 = Gradient(x0, z0, t0, frequency);
	Gradient3d grad001 = Gradient(x0, z0, t1, frequency);
	Gradient3d grad010 = Gradient(x0, z1, t0, frequency);
	Gradient3d grad011 = Gradient(x0, z1, t1, frequency);
	Gradient3d grad100 = Gradient(x1, z0, t0, frequency);
	Gradient3d grad101 = Gradient(x1, z0, t1, frequency);
	Gradient3d grad110 = Gradient(x1, z1, t0, frequency);
	Gradient3d grad111 = Gradient(x1, z1, t1, frequency);	
		
	float dx0 = xp - x0;
	float dx1 = xp - x1;
		
	float dz0 = zp - z0;	
	float dz1 = zp - z1;
		
	float dt0 = tp - t0;
	float dt1 = tp - t1;
			
	/* function value at each of 8 surround points */
	float f000 = grad000.x * dx0 + grad000.z * dz0 + grad000.t * dt0;
	float f001 = grad001.x * dx0 + grad001.z * dz0 + grad001.t * dt1;
	float f010 = grad010.x * dx0 + grad010.z * dz1 + grad010.t * dt0;
	float f011 = grad011.x * dx0 + grad011.z * dz1 + grad011.t * dt1;
	float f100 = grad100.x * dx1 + grad100.z * dz0 + grad100.t * dt0;
	float f101 = grad101.x * dx1 + grad101.z * dz0 + grad101.t * dt1;
	float f110 = grad110.x * dx1 + grad110.z * dz1 + grad110.t * dt0;
	float f111 = grad111.x * dx1 + grad111.z * dz1 + grad111.t * dt1;
		
	/* Bi-cubic interpolation */
	float coefX = 3*dx0*dx0 - 2*dx0*dx0*dx0;
	float coefZ = 3*dz0*dz0 - 2*dz0*dz0*dz0;
	float coefT = 3*dt0*dt0 - 2*dt0*dt0*dt0;
	
	float p1x = f000 + coefX*(f100 - f000);
	float p2x = f010 + coefX*(f110 - f010);
	float p3z = p1x + coefZ*(p2x - p1x);
	
	float p4x = f001 + coefX*(f101 - f001);
	float p5x = f011 + coefX*(f111 - f011);
	float p6z = p4x + coefZ*(p5x - p4x);
	
	float p7t = p3z + coefT*(p6z - p3z);	

	return p7t;
}

int PerlinNoise::PerlinNoise3d(int x, int z, float t, int width, int height) {
	float total = 0;
	for (int i = 0; i < rankCount; i++) {
		total += PerlinNoise3d(				
				(float)x / (width-1) * frequency[i] , 
				(float)z  / (height-1) * frequency[i], 
				(float) (t) * frequency[i],
				frequency[i]
				) /* amplitude */ * amplitude[i];
	}
	if (total < -1 || total > 1) 
		printf("%.2f\n", total);
	
	return (int)((total + 1.0f) * 255 / 2.0f);
}
